﻿using System;
using System.Collections.Generic;
using System.Data;
using System.Xml;
using System.Text;
using SdmxMl.Helper;
using SdmxMl.Common;
using SdmxMl.Manager;
using System.Windows.Forms;

namespace SdmxMl.Structure
{
    /// <summary>
    /// Base Class for KeyFamily dimension or attribute
    /// </summary>
    public class BaseDimensionType: IAnnotationList
    {
        #region Construction

        public BaseDimensionType()
        {
            Init();
        }

        private void Init()
        {
            _codelistInfo = new CodelistReference();
            _conceptInfo = new ConceptReference();
            _conceptSchemeInfo = new ConceptSchemeReference();
            textFormat = new TextFormatType();
            AnnotationList = new AnnotationList();
        }

        public BaseDimensionType(XmlNode nodeArtefact, SdmxNamespaceManager nsmgr) 
        {
            string query = string.Empty;
            if (nsmgr.VersionSdmx == SdmxVersion.SDMX_20)
            {
                AnnotationList = new AnnotationList();
                _conceptInfo = new ConceptReference(nodeArtefact, nsmgr);
                Id = _conceptInfo.Id;
                _conceptSchemeInfo = new ConceptSchemeReference(nodeArtefact, nsmgr);
                _codelistInfo = new CodelistReference(nodeArtefact, nsmgr);
                textFormat = new TextFormatType(nodeArtefact, nsmgr);

                // Annotations ?
                XmlNodeList nodeList = nodeArtefact.SelectNodes("./structure:Annotations/common:Annotation", nsmgr);
                foreach (XmlNode na in nodeList)
                {
                    Annotation a = new Annotation(na, nsmgr);
                    AnnotationList.Add(a);
                }
                Annotation ann = AnnotationList.GetAnnotationType("SDMX21_ID");
                if (ann != null)
                {
                    Id = ann.Title; 
                    AnnotationList.Remove(ann);
                }

            }
            else
            {
                Init();
                Id = Xmlhelper.GetAtt(nodeArtefact, "id");

                // Annotations ?
                XmlNodeList nodeList = nodeArtefact.SelectNodes("./common:Annotations/common:Annotation", nsmgr);
                foreach (XmlNode na in nodeList)
                {
                    Annotation a = new Annotation(na, nsmgr);
                    AnnotationList.Add(a);
                }

                XmlNode ndConceptIdentity = nodeArtefact.SelectSingleNode("./structure:ConceptIdentity/Ref", nsmgr);
                if (ndConceptIdentity != null)
                {
                    _conceptInfo.Id = Xmlhelper.GetAtt(ndConceptIdentity, "id");
                    _conceptSchemeInfo.Id = Xmlhelper.GetAtt(ndConceptIdentity, "maintainableParentID");
                    _conceptSchemeInfo.AgencyId = Xmlhelper.GetAtt(ndConceptIdentity, "agencyID");
                    _conceptSchemeInfo.Version = Xmlhelper.GetAtt(ndConceptIdentity, "maintainableParentVersion");
                }
                else
                {
                    // Try via URN
                    query = "./structure:ConceptIdentity/URN";
                    XmlNode ndCoreRef = nodeArtefact.SelectSingleNode(query, nsmgr);
                    if (ndCoreRef != null)
                    {
                        string urn = UrnParser.UrnSdmx_21_to_20(ndCoreRef.InnerText);
                        UrnParser p = new UrnParser(urn);
                        _conceptSchemeInfo.AgencyId = p.AgencyId;
                        _conceptSchemeInfo.Id = p.ArtefactId;
                        _conceptSchemeInfo.Version = p.Version;
                        _conceptInfo.Id = p.SubTypeId;
                    }
                }
                
                // Has Local Representation ?

                // Has TextFormat
                query = "./structure:LocalRepresentation";
                XmlNode ndTF = nodeArtefact.SelectSingleNode(query, nsmgr);
                if (ndTF != null)
                    TextFormat = new TextFormatType(ndTF, nsmgr);

                // Has codelist
                XmlNode ndCL = nodeArtefact.SelectSingleNode("./structure:LocalRepresentation/structure:Enumeration/Ref", nsmgr);
                if (ndCL != null)
                {
                    _codelistInfo.Id = Xmlhelper.GetAtt(ndCL, "id");
                    _codelistInfo.AgencyId = Xmlhelper.GetAtt(ndCL, "agencyID");
                    _codelistInfo.Version = Xmlhelper.GetAtt(ndCL, "version");
                }
                else
                {
                    // Try via URN
                    query = "./structure:LocalRepresentation/structure:Enumeration/URN";
                    XmlNode ndCoreRef = nodeArtefact.SelectSingleNode(query, nsmgr);
                    if (ndCoreRef != null)
                    {
                        string urn = UrnParser.UrnSdmx_21_to_20(ndCoreRef.InnerText);
                        UrnParser p = new UrnParser(urn);
                        _codelistInfo.AgencyId = p.AgencyId;
                        _codelistInfo.Id = p.ArtefactId;
                        _codelistInfo.Version = p.Version;
                    }
                }

            }
        }
        #endregion

        #region XML serialize

        protected void WriteReferences(XmlTextWriter xw, string prefix)
        {
            WriteReferences(xw, prefix, true);
        }

        protected void WriteAnnotationsAndId(XmlTextWriter xw, string prefix)
        {
            // Annotations
            Annotation annId = null;
            // Support new SDMX 2.1 ID attribute as Annotations
            if (string.IsNullOrEmpty(Id) == false && Id != this.ConceptInfo.Id)
            {
                annId = new Annotation("SDMX21_ID");
                annId.Title = Id;
                AnnotationList.Insert(0, annId);
            }

            AnnotationList.WriteXml(xw, prefix, null);

            if (annId != null)
                AnnotationList.Remove(annId);
        }

        protected void WriteReferences(XmlTextWriter xw, string prefix, bool setTextFormat)
        {
            if (SdmxNamespaceManager.SdmxTarget == SdmxVersion.SDMX_20)
            {
                ConceptInfo.WriteXml(xw);
                ConceptSchemeInfo.WriteXml(xw);
                CodelistInfo.WriteXml(xw);
            }
            else
            {
                
                if (string.IsNullOrEmpty(Id))
                    Id = ConceptInfo.Id;
                Xmlhelper.WriteAttributeIfExist(xw, "id", Id);

                // Annotations
                AnnotationList.WriteXml(xw, "common", null);

                if (ConceptSchemeInfo.IsFilled)
                {
                    ConceptType c = new ConceptType();
                    c.Id = ConceptInfo.Id;
                    string urn = UrnManager.FullIdToUrn(SdmxArtefactType.Concepts, ConceptSchemeInfo.FullIdent, c, null);
                    xw.WriteStartElement(prefix, "ConceptIdentity", null);
                    xw.WriteElementString("URN", UrnParser.UrnSdmx_20_to_21(urn));
                    xw.WriteEndElement();
                }
                if (CodelistInfo.IsFilled)
                {
                    xw.WriteStartElement(prefix, "LocalRepresentation", null);
                    xw.WriteStartElement(prefix, "Enumeration", null);
                    string urn = UrnManager.FullIdToUrn(SdmxArtefactType.CodeLists, CodelistInfo.FullIdent);
                    xw.WriteElementString("URN", UrnParser.UrnSdmx_20_to_21(urn));
                    xw.WriteEndElement();
                    xw.WriteEndElement();
                }
                // Text Format only if no codelist ref
                else if (setTextFormat)
                {
                    if (Id == "TIME_PERIOD")
                    {
                        xw.WriteStartElement(prefix, "LocalRepresentation", null);
                        xw.WriteElementString(prefix, "TextFormat", null, null);
                        xw.WriteEndElement();
                    }
                    else
                    {
                        if (TextFormat.TextType == TextTypeType.Undefined)
                            TextFormat.TextType = TextTypeType.String;

                        xw.WriteStartElement(prefix, "LocalRepresentation", null);
                        TextFormat.WriteXml(xw, prefix, "TextFormat");
                        xw.WriteEndElement();
                    }
                }

            }
        }
        #endregion

        #region Properties

        private ConceptReference _conceptInfo;
        public ConceptReference ConceptInfo
        {
            get { return _conceptInfo; }
        }

        private ConceptSchemeReference _conceptSchemeInfo;
        public ConceptSchemeReference ConceptSchemeInfo
        {
            get { return _conceptSchemeInfo; }
        }

        private CodelistReference _codelistInfo;
        public CodelistReference CodelistInfo
        {
            get { return _codelistInfo; }
        }

        protected TextFormatType textFormat;
        public TextFormatType TextFormat
        {
            get { return textFormat; }
            set { textFormat = value; }
        }

        /// <summary></summary>
        private AnnotationList annotationList;
        public AnnotationList AnnotationList
        {
            get { return annotationList; }
            set { annotationList = value; }
        }

        //sdmx 2.1
        public string Id { get; set; }

        #endregion

        #region Tree

        public TreeNode FillNode(TreeNode parentNode)
        {
            TreeNode t = new TreeNode(ToString());
            t.Tag = this;
            parentNode.Nodes.Add(t);
            return t;
        }
        #endregion

        #region String Representation

        public override string ToString()
        {
            return ConceptInfo.Id;
        }
        #endregion
    }


    /// <summary>Codelist artefact reference Information </summary>
    public class CodelistReference : ArtefactRefBase
    {
        #region Construction

        public CodelistReference() : base()
        {
        }

        public CodelistReference(XmlNode nodeArtefact, XmlNamespaceManager nsmgr)
        {
            Id = Xmlhelper.GetAtt(nodeArtefact, "codelist");
            Version = Xmlhelper.GetAtt(nodeArtefact, "codelistVersion");
            AgencyId = Xmlhelper.GetAtt(nodeArtefact, "codelistAgency");
        }
        #endregion

        #region XML Representation

        public void WriteXml(XmlTextWriter xw)
        {
            if (string.IsNullOrEmpty(Id) == false)
            {
                Xmlhelper.WriteAttributeIfExist(xw, "codelist", Id);
                Xmlhelper.WriteAttributeIfExist(xw, "codelistVersion", Version);
                Xmlhelper.WriteAttributeIfExist(xw, "codelistAgency", AgencyId);
            }
        }

        #endregion //XMl Representation

    }

    /// <summary>
    /// Concept Reference, deprecated usage for SDMX 1.0, only concept ref (Id) used
    /// </summary>
    public class ConceptReference : ArtefactRefBase
    {
        #region Construction
        public ConceptReference()
            : base()
        {
        }

        public ConceptReference(XmlNode nodeArtefact, XmlNamespaceManager nsmgr)
        {
            Id = Xmlhelper.GetAtt(nodeArtefact, "conceptRef");
            Version = Xmlhelper.GetAtt(nodeArtefact, "conceptVersion");
            AgencyId = Xmlhelper.GetAtt(nodeArtefact, "conceptAgency");
        }

        #endregion //Construction

        #region XML Representation

        public void WriteXml(XmlTextWriter xw)
        {
            // Required
            xw.WriteAttributeString("conceptRef", Id);

            if (Version != "1.0")
                Xmlhelper.WriteAttributeIfExist(xw, "conceptVersion", Version);
            Xmlhelper.WriteAttributeIfExist(xw, "conceptAgency", AgencyId);
        }

        #endregion //XML Representation
    }


    /// <summary>Concept Scheme reference INFORMATION </summary>
    public class ConceptSchemeReference : ArtefactRefBase
    {

        #region Construction
        
        public ConceptSchemeReference() : base()
        {
        }

        public ConceptSchemeReference(XmlNode nodeArtefact, XmlNamespaceManager nsmgr)
        {
            Id = Xmlhelper.GetAtt(nodeArtefact, "conceptSchemeRef");
            Version = Xmlhelper.GetAtt(nodeArtefact, "conceptSchemeVersion");
            AgencyId = Xmlhelper.GetAtt(nodeArtefact, "conceptSchemeAgency");
        }
        #endregion //Construction

        #region XML Representation

        public void WriteXml(XmlTextWriter xw)
        {

            Xmlhelper.WriteAttributeIfExist(xw, "conceptSchemeRef", Id);
            if (Version != "1.0")
                Xmlhelper.WriteAttributeIfExist(xw, "conceptSchemeVersion", Version);
            Xmlhelper.WriteAttributeIfExist(xw, "conceptSchemeAgency", AgencyId);
        }

        #endregion // XML Representation
    }

    /// <summary>
    /// Basic container for key family (DSD) Components
    /// </summary>
    public class BaseDimensionTypeList : List<BaseDimensionType>
    {
        #region Properties

        private KeyFamilyType _parentDsd;
        public KeyFamilyType ParentDsd
        {
            get { return _parentDsd; }
        }

        #endregion // Properties

        #region Constructor

        public BaseDimensionTypeList(KeyFamilyType dsd)
            : base()
        {
            _parentDsd = dsd;
        }

        #endregion //Constructor

        #region Table representation

        protected virtual DataTable DefineDataTableRepresentation()
        {
            DataTable dataTable = new DataTable();
            dataTable.Columns.Add(new DataColumn("Ref", typeof(bool)));
            dataTable.Columns.Add(new DataColumn("Type", typeof(Object)));
            dataTable.Columns.Add(new DataColumn("Role", GetRoleEnumType()));
            dataTable.Columns.Add(new DataColumn("Concept", typeof(string)));
            dataTable.Columns.Add(new DataColumn("AttLevel", typeof(string)));
            dataTable.Columns.Add(new DataColumn("Cross", typeof(string)));
            dataTable.Columns.Add(new DataColumn("Name", typeof(string)));
            dataTable.Columns.Add(new DataColumn("Codelist", typeof(string)));
            dataTable.Columns.Add(new DataColumn("AssignStatus", typeof(string)));
            dataTable.Columns.Add(new DataColumn("TextType", typeof(string)));
            dataTable.Columns.Add(new DataColumn("Measure", typeof(string)));
            dataTable.Columns.Add(new DataColumn("Code", typeof(string)));

            return dataTable;
        }

        public virtual DataTable GetTableRepresentation()
        {
            DataTable dataTable = DefineDataTableRepresentation();

            foreach (BaseDimensionType bd in this)
                dataTable.Rows.Add(GetRowObjects(bd).ToArray());

            return dataTable;
        }

        #endregion

        #region Public Methods

        protected ConceptType cType = null;
        protected CodeListType clType = null;

        /// <summary>
        /// Inform of refered codelist, can be direct from basedimension or if none
        /// derived from concept
        /// </summary>
        /// <param name="bd">the variabler as base class</param>
        /// <returns>the codelist id</returns>
        protected string GetReferedCodelistId(BaseDimensionType bd)
        {
            string co = string.Empty;
            if (bd.CodelistInfo.IsFilled)
                co = bd.CodelistInfo.Id + "+" + bd.CodelistInfo.Version;
            else if (cType != null && cType.CoreRepresentation.IsFilled)
                co = "© " + cType.CoreRepresentation.Id + "+" + cType.CoreRepresentation.Version; ;
            return co;
        }

        protected string GetReferedTextFormat(BaseDimensionType bd)
        {
            string tf = TextTypeType.Undefined.ToString();
            if (bd.TextFormat.TextType != TextTypeType.Undefined)
                tf = bd.TextFormat.TextType.ToString();
            else if (cType != null && cType.TextFormat.TextType != TextTypeType.Undefined)
                tf = "© " + cType.TextFormat.TextType.ToString();
            return tf;
        }


        public virtual List<object> GetRowObjects(BaseDimensionType bd)
        {
            List<object> lo = new List<object>(12);
            cType = ReferenceManager.LocateConcept(bd.ConceptSchemeInfo, bd.ConceptInfo.Id);
            string conceptName = cType == null ? string.Empty : cType.Name.ToString();

            clType = ReferenceManager.LocateCodelist(bd.CodelistInfo);
            string codelistName = clType == null ? string.Empty : bd.CodelistInfo.Version + " - " + clType.Name.ToString();
            lo.Add(cType != null);
            lo.Add(System.DBNull.Value); // Icon
            lo.Add(GetKind(bd));
            lo.Add(bd.ConceptInfo.Id);
            lo.Add(string.Empty);
            lo.Add(GetCrossAttachText(bd));
            lo.Add(conceptName);
            lo.Add(GetReferedCodelistId(bd));
            lo.Add(string.Empty);
            lo.Add(GetReferedTextFormat(bd));
            lo.Add(string.Empty);
            lo.Add(string.Empty);

            return lo;
        }

        /// <summary>Type for enumeration in role</summary>
        /// <returns></returns>
        protected virtual Type GetRoleEnumType()
        {
            return typeof(DimensionType.DimensionKind);
        }


        public virtual DimensionType.DimensionKind GetKind(BaseDimensionType bd)
        {
            return DimensionType.DimensionKind.Dimension;
        }

        public virtual string GetCrossAttachText(BaseDimensionType bd)
        {
            return string.Empty;
        }

        public virtual BaseDimensionType NewItem()
        {
            return null;
        }

        public BaseDimensionType GetDimensionFromName(string name)
        {
            foreach (BaseDimensionType dim in this)
                if (dim.ConceptInfo.Id == name)
                    return dim;
            return null;
        }
        #endregion
    }

}
